all files / src/ julian-date.ts

92.54% Statements 62/67
75.51% Branches 37/49
91.67% Functions 11/12
91.94% Lines 57/62
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 1241× 1× 1× 1×             1×   1× 1× 9523×     621099× 621098× 621098×   621098×           1× 166944× 166944× 166944×   166944× 166944× 166944× 166944× 166944×   166944× 166944× 139728×   27216×   166944× 166944× 139728×   27216×   166944×   166944×           1×                     1× 621098×       621098×           322890× 322890× 322890×   161445× 26227× 26227×     161445×               1× 621097×         1× 226701× 500× 500× 500×     1× 160945× 160945×     1× 1001×     1× 3×   1×  
import { isLeapYear, parseDateString, JulianDays } from "./common";
import { HistoricalDate } from './historical-date';
import { InvalidDateException } from './invalid-date-exception';
import { GregorianDate } from "./gregorian-date";
 
// Licensed under the MIT license:
// http://opensource.org/licenses/MIT
// Copyright (c) 2016, fitnr <fitnr@fakeisthenewreal>
// based on https://github.com/fitnr/convertdate/blob/5a1d1323c34670c5e179900d8770db59c009e679/convertdate/julian.py
 
const HAVE_30_DAYS = [4, 6, 9, 11];
 
export class JulianDate extends HistoricalDate {
    public get isLeapYear() {
        return isLeapYear(this.year, this.calendar);
    }
 
    constructor(public readonly year: number | undefined,
        public readonly month: number | undefined,
        public readonly day: number | undefined) {
        super('julian');
        this.assertLegalDate(year, month, day);
    }
 
    /**
     * Creates a new JulianDate object based on the number of Julian days.
     */
    static fromJulianDays(jd: JulianDays) {
        let days = jd.days;
        days += 0.5;
        let z = Math.trunc(days);
 
        let a = z;
        let b = a + 1524;
        let c = Math.trunc((b - 122.1) / 365.25);
        let d = Math.trunc(365.25 * c);
        let e = Math.trunc((b - d) / 30.6001);
 
        let month: number;
        if (e < 14) {
            month = e - 1;
        } else {
            month = e - 13;
        }
        let year: number;
        if (month > 2) {
            year = c - 4716;
        } else {
            year = c - 4715;
        }
        let day = b - d - Math.trunc(30.6001 * e);
 
        return new JulianDate(
            jd.unknownYear ? undefined : year,
            jd.unknownMonth ? undefined : month,
            jd.unknownDay ? undefined : day);
    }
 
    static fromString(text: string) {
        const parsed = parseDateString(text);
        if (parsed) {
            return new JulianDate(parsed.year, parsed.month, parsed.day);
        }
        throw new InvalidDateException();
    }
 
    /**
     * Check if this is a legal date in the Julian calendar
     */
    private assertLegalDate(year: number | undefined, month: number | undefined, day: number | undefined) {
        Iif (day !== undefined && (day < 0 || day > this.monthLength(year, month))) {
            throw new InvalidDateException(`Month ${month} doesn't have a day ${day}`);
        }
 
        return true;
    }
 
    /**
     * Convert to Julian day using astronomical years (0 = 1 BC, -1 = 2 BC)
     */
    private toJulianDays(Eyear: number = this.year || 1,
        Emonth: number = this.month || 1,
        Eday: number = this.day || 1) {
        // Algorithm as given in Meeus, Astronomical Algorithms, Chapter 7, page 61
        if (month <= 2) {
            year -= 1;
            month += 12;
        }
 
        return {
            days: (Math.trunc((365.25 * (year + 4716))) + Math.trunc((30.6001 * (month + 1))) + day) - 1524.5,
            unknownYear: this.year === undefined,
            unknownMonth: this.month === undefined,
            unknownDay: this.day === undefined
        }
    }
 
    private monthLength(year: number | undefined, month: number | undefined) {
        return month == 2
            ? isLeapYear(year, 'julian') ? 29 : 28
            : HAVE_30_DAYS.indexOf(month || 1) >= 0 ? 30 : 31;
    }
 
    public addDays(days: number) {
        if (days == 0) { return this; }
        const jd = this.toJulianDays();
        jd.days += days;
        return JulianDate.fromJulianDays(jd);
    }
 
    public toGregorian() {
        const julianDays = this.toJulianDays();
        return GregorianDate.fromJulianDays(julianDays);
    }
 
    public toJulian() {
        return this;
    }
 
    public toString() {
        return `${this.year === undefined ? '??' : this.year}-${this.month === undefined ? '??' : this.month}-${this.day === undefined ? '??' : this.day} (Julian)`;
    }
}